<?php


namespace BeReborn\Database\Pool;


use BeReborn\Exception\DbException;
use Swoole\Coroutine;
use Exception;
use PDO;

/**
 * Class DbConnection
 * @package BeReborn\Database\Pool
 */
class DbConnection extends Pool
{
    public $index = 0;

    /** @var PDO[] $_pdo */
    private $_pdo = [];

    /** @var int[] */
    private $inTransaction = [];

    private $_releaseTime = 0;


    /**
     * @return int
     */
    public function getPdoCount()
    {
        return count($this->_pdo);
    }

    /**
     * @return int
     * 获取当前连接长度
     */
    public function getTotal()
    {
	    return $this->getPdoCount() + $this->getChannelLength();
    }

    /**
     * @return bool
     */
    public function inTransaction()
    {
        if (!isset($this->inTransaction[$this->getCid()])) {
            return false;
        }
        return $this->inTransaction[$this->getCid()] >= 1;
    }


    /**
     * 初始化
     */
    public function initialization()
    {
        if (!isset($this->inTransaction[$this->getCid()])) {
            $this->inTransaction[$this->getCid()] = 0;
        }
        if ($this->inTransaction[$this->getCid()] > 0) {
            $this->inTransaction[$this->getCid()] = 0;
        }
    }

    /**
     * @throws Exception
     */
    public function beginTransaction()
    {
        if (!isset($this->inTransaction[$this->getCid()])) {
            $this->inTransaction[$this->getCid()] = 0;
        }
        if ($this->inTransaction[$this->getCid()] === 0) {
            $this->getConnect()->beginTransaction();
        }
        $this->inTransaction[$this->getCid()]++;
    }

    /**
     * @throws Exception
     */
    public function commit()
    {
        if (!isset($this->inTransaction[$this->getCid()])) {
            $this->inTransaction[$this->getCid()] = 0;
        } else {
            $this->inTransaction[$this->getCid()]--;
            if ($this->inTransaction[$this->getCid()] === 0) {
                $this->getConnect()->commit();
            }
        }
    }

    /**
     * @throws Exception
     */
    public function rollback()
    {
        if (!isset($this->inTransaction[$this->getCid()])) {
            $this->inTransaction[$this->getCid()] = 0;
        } else {
            $this->inTransaction[$this->getCid()]--;
            if ($this->inTransaction[$this->getCid()] === 0) {
                $this->getConnect()->rollBack();
            }
        }
    }

    /**
     * @return bool
     * @throws Exception
     */
    public function isConnect()
    {
        try {
            $connect = $this->getPdo();
            if (empty($connect) || !($connect instanceof PDO)) {
                throw new Exception('Unbale mysql client.');
            }
            if (!$connect->getAttribute(PDO::ATTR_SERVER_INFO)) {
                throw new Exception('Mysql cient timeout.');
            }
            return true;
        } catch (\Error | Exception $exception) {
            $this->offPdo($this->getCid());
            $this->addError($exception->getMessage());
            return false;
        }
    }

    /**
     * @return PDO
     * @throws Exception
     */
    public function getConnect(): PDO
    {
        if ($this->_releaseTime == 0) {
            $this->_releaseTime = time();
        }
        if ($this->getTick() === null) {
            $this->timeTick();
        }
        if ($this->hasPdo()) {
            return $this->getPdo();
        }
        if (!($pdo = parent::getConnect())) {
            throw new DbException('链接数过多， 请稍后再试!');
        }
        return $this->addPdo($pdo);
    }

    /**
     * @return PDO|null
     */
    private function getPdo()
    {
        if (!isset($this->_pdo[$this->getCid()])) {
            return null;
        }

        [$pdo, $createTime] = $this->_pdo[$this->getCid()];
        return $pdo;
    }

    /**
     * @return bool
     */
    private function hasPdo()
    {
        return isset($this->_pdo[$this->getCid()]);
    }

    /**
     * @param $pdo
     * @return mixed
     */
    private function addPdo($pdo)
    {
        if ($this->hasPdo()) {
            $this->offPdo($this->getCid());
        }
        $this->_pdo[$this->getCid()] = [$pdo, time()];
        return $pdo;
    }


    /**
     * @param $cid
     * @return bool
     */
    private function offPdo($cid = null)
    {
        if ($cid === null) {
            $cid = $this->getCid();
        }
        unset($this->_pdo[$cid]);
        return isset($this->_pdo[$cid]);
    }

    /**
     * @return int
     */
    public function getReleaseTime()
    {
        return $this->_releaseTime;
    }


    /**
     * 如果存在事务，则不释放
     * @throws
     */
    public function coroutineRelease()
    {
        if (!$this->hasPdo()) {
            return;
        }

        $this->_releaseTime = time();
        $pdo = $this->getPdo();
        $pdo->inTransaction() && $pdo->commit();

        $this->initialization();
        parent::release($pdo); // TODO: Change the autogenerated stub

        $this->offPdo($this->getCid());
    }


    /**
     * 如果存在事务，则不释放
     * @throws
     */
    public function release_all_connections()
    {
        if (!is_array($this->_pdo)) {
            return;
        }
        foreach ($this->_pdo as $pdo) {
            [$pdo, $timer] = $pdo;
            $pdo->inTransaction() && $pdo->commit();

            $this->initialization();
            parent::release($pdo);
        }

        $this->_releaseTime = time();
        if ($this->_tick === null) {
            $this->timeTick();
        }
        $this->_pdo = [];
    }

    /**
     * 回收链接，用来保证高流量时有链接可用
     */
    public function recovery()
    {
        if (!$this->hasPdo() || $this->inTransaction()) {
            return;
        }

        $this->_releaseTime = time();
        $this->initialization();
        parent::release($this->getPdo());

        $this->offPdo($this->getCid());
    }

    /**
     * 强行销毁
     * @throws
     */
    public function destroy()
    {
        if (!$this->hasPdo()) {
            return $this;
        }
        $pdo = $this->getPdo();
        if ($pdo instanceof PDO) {
            $pdo->inTransaction() && $pdo->commit();
            $this->initialization();
        }
        $this->offPdo($this->getCid());
        return $this;
    }

    /**
     * 释放链接
     */
    public function disconnect()
    {
        $this->offPdo($this->getCid());
    }

    /**
     * @param array $config
     * @return PDO
     * @throws
     */
    public function createConnect(array $config = [])
    {
        try {
            $link = new PDO($config['cds'], $config['username'], $config['password'], [
                PDO::ATTR_EMULATE_PREPARES => false,
                PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
                PDO::ATTR_CASE => PDO::CASE_NATURAL,
            ]);
            $link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $link->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
            $link->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_EMPTY_STRING);
            return $link;
        } catch (\Throwable $exception) {
            if ($exception->getCode() !== 2006) {
                $this->addError($config['cds'] . '  ->  ' . $exception->getMessage());
                throw new Exception($exception->getMessage());
            }
            $this->addError($config['cds'] . '  ->  ' . $exception->getMessage());
            return $this->createConnect($config);
        }
    }

    /**
     * @throws Exception
     */
    public function getConnectConfig()
    {
        throw new Exception('You need add client configure on return.');
    }

    /**
     * @return mixed
     */
    public function getCid()
    {
        return Coroutine::getCid();
    }
}
